iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Software Development

Unity進階——做一個完整的遊戲系列 第 23

Day 23:升級多說話者對話與主角基礎動作

  • 分享至 

  • xImage
  •  

前一天我建立了酒館室內場景,並在背景人物身上加上互動區。玩家已經可以靠近酒館人物並觸發對話,但當時的對話系統還有一個明顯限制:一整段對話只能顯示同一個說話者。

這在早期村長任務還能勉強使用,因為大多是 NPC 說明任務。但酒館劇情不一樣。主角需要追問,NPC 需要回答,兩邊會來回交錯。如果整段都顯示成同一個 NPC 在說話,就會變成 NPC 自問自答。

所以 Day 23 前半的重點,是升級對話系統,讓同一段對話可以逐句指定說話者。後半則是更換主角素材,補上走路、跑步、四方向顯示與拳擊攻擊,讓主角不再只是測試用佔位角色。

首先,我先檢查原本對話系統的限制。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373EnfYZSkTzy.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373nslbF9hFzQ.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373PSZkrcqouu.png

原本的 NpcDialogueInteraction.cs 裡有 speakerName 和 dialogueLines。這代表一個 NPC 可以有多句台詞,但整段對話只會使用同一個 speaker 名稱。

第 33 到 44 行保留了舊版 speakerName、interactPrompt 和 dialogueLines。
第 75 到 106 行仍然使用原本的互動流程:玩家靠近互動區後,按 E 或 Space 開啟或推進對話。
第 344 到 374 行取得目前台詞時,如果沒有啟用新版逐句 speaker 資料,就會回到舊版 dialogueLines。
這樣設計的好處是:Day 19 和 Day 21 已經做好的舊 NPC 不需要全部重做。
壞處也很明顯:如果劇情需要主角和 NPC 來回說話,舊系統就不夠用了。它只能讓一個人把所有台詞講完。因此,我在保留舊系統的前提下,新增多說話者資料。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373GbTHRmHHfs.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373hMwnOn4uhZ.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373tq5g1Z6fdf.png

我新增 DialogueLineEntry,讓每一句對話都能保存自己的 speakerName 和 text。也就是說,不再是一段對話共用一個說話者,而是每一句都可以指定誰在說話。
第 9 到 14 行新增 DialogueLineEntry。
第 39 到 40 行在 NPC 本體上新增 useSpeakerLines 和 speakerLines。
第 302 到 320 行計算對話總行數時,會優先檢查 speakerLines;如果沒有啟用新版資料,才回到舊版 dialogueLines。

這樣就能同時支援兩種模式:
舊 NPC 繼續用 speakerName + dialogueLines。新劇情使用 speakerLines,逐句指定說話者。
這種寫法比較安全,因為我不需要把前面做好的村長或提示對話全部拆掉重做。接著,我修改對話顯示邏輯,讓畫面能依照目前行數取得說話者與台詞。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373pT0V7dYIXD.png
https://ithelp.ithome.com.tw/upload/images/20260921/2017837325w8rixqw8.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373xhb3tUdgTF.png

第 274 到 300 行的 OnGUI() 會繪製對話框、說話者名稱、目前台詞,以及底部的下一句提示。
第 322 到 342 行的 GetCurrentSpeakerName() 會依照目前 lineIndex 取得這一句的說話者。如果該句沒有填 speaker,就使用這段對話的預設 speaker。
第 344 到 374 行的 GetCurrentLineText() 會依照目前 lineIndex 取得台詞文字,同時保留新版 speakerLines 和舊版 dialogueLines 的 fallback。

也就是說,顯示對話時,系統會先看目前這一句是不是新版逐句 speaker 資料。
如果是,就顯示這一句自己的 speaker 和 text。
如果不是,就回到原本的單 speaker 對話。
這樣酒館人物可以和主角互相對話,而舊 NPC 還能繼續正常顯示。接著把新版多 speaker 對話套用到酒館。

https://ithelp.ithome.com.tw/upload/images/20260921/201783738bCMUTjjzY.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373e8ZpyDKjJ0.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373jfum7jtkwy.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373y9krgO6moY.png

Day 22 的酒館背景人物其實是畫在背景圖上的,不是獨立角色 Sprite。
因此我仍然使用透明 Trigger 作為互動區,例如 BackgroundCharacter_01_Bar、BackgroundCharacter_02_Pillar 和 BackgroundCharacter_03_LowerHall。
這次更新的不是背景圖,而是每個互動區上的 NpcDialogueInteraction。
在 Inspector 中可以看到 Use Speaker Lines 和 Speaker Lines,裡面逐句填入 酒館老闆 / 主角、阿森 / 主角 或 洛克 / 主角 的對話。
Day23MultiSpeakerDialogueOrganizer.cs
第 61 到 117 行保存酒館老闆、阿森、洛克與主角交錯的對話文字。
第 218 到 255 行負責開啟酒館場景,並把三個背景人物互動區改成多 speaker 對話。
第 376 到 408 行則實際寫入 dialogueSequences,逐句填入 speakerName 和 text。
工具只是幫我把手動設定加速完成。正常 Unity 流程其實就是:選取互動區、啟用多 speaker、逐句填說話者和台詞。只是人類手動填幾十行對話太容易填錯,工具至少能降低我對 Inspector 的厭世感。接著進入 Play Mode 測試多 speaker 對話。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373ixlbCHnYR4.png

測試時,玩家靠近酒館背景人物,按下互動鍵後,對話框會依照每一句資料切換說話者。
例如前一句是酒館老闆,下一句就能切到主角。這代表酒館追問劇情可以按照劇本呈現,而不是所有台詞都顯示成同一個 NPC 。除了酒館人物,我也把第一次村長對話改成多 speaker。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373A3mZfIfbXI.png

第一次找村長的劇情本來就不是村長單方面交代任務。主角會先回報遺跡異常,村長再補充森林狀況,兩人討論後才決定下一步去酒館詢問受傷的年輕人。所以我也把 ChiefInteractionArea 改成使用 Speaker Lines。
這樣村長與主角可以逐句切換說話者,而不是整段都顯示成村長一個人在講。

對話完成後,左上角目前目標仍然會更新為:
前往酒館,詢問受傷的年輕人
這一步確認了兩件事:
第一,村長對話已經支援多說話者。
第二,原本 Day 21 的目標更新流程沒有被破壞。
接著,我加入主線階段限制,避免玩家跳過酒館直接觸發第二次村長回報。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373OYSL2ekZdN.png
https://ithelp.ithome.com.tw/upload/images/20260921/201783737s1ZqgiOBz.png
https://ithelp.ithome.com.tw/upload/images/20260921/201783738nawBk4ngj.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373vkrDFXqBRy.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373oRmgqq2VYK.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373yioz6aoOXe.png

如果同一個村長互動區同時有第一次對話和第二次回報對話,就不能只靠替換內容來處理。
否則玩家可能第一次見村長時,就觸發酒館後的回報劇情。這種劇情跳關會讓整個主線像被人從中間抽走幾頁。
因此我加入 DialogueSequence。

第 17 到 30 行新增 DialogueSequence,讓一段對話可以指定自己的任務階段、預設說話者、逐句台詞、完成後目標文字與下一個任務階段。
第 245 到 264 行處理對話完成後的行為。如果目前序列勾選 setObjectiveOnComplete,就更新左上角目前目標;如果勾選 advanceQuestStageOnComplete,就推進 MainQuestState。
第 387 到 415 行的 GetActiveSequence() 會讀取目前 MainQuestStage,只挑選符合 requiredQuestStage 的對話序列。
第 468 到 536 行則定義 MainQuestStage 和 MainQuestState,並用 DontDestroyOnLoad 保存目前階段,讓玩家跨場景後仍然保留劇情進度。

這樣每段對話都有自己的出現條件。不是目前階段的對話,不會被播放。劇情終於不用靠祈禱維持順序,真是程式設計的一小步。接著設定村長與酒館的任務階段對話。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373KptpbdxkYw.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373fNQ5qFuJnA.png
https://ithelp.ithome.com.tw/upload/images/20260921/201783732IBLJmbpKZ.png

現在 ChiefInteractionArea 同時保存兩段對話:
ReportToChief:第一次找村長。
ReturnToChief:酒館調查後回報村長。
第一次村長對話完成後,任務階段會推進到 TavernInvestigation,目前目標更新為去酒館。
酒館洛克對話完成後,任務階段推進到 ReturnToChief,目前目標更新為回村長家回報洞穴線索。
第二次村長回報完成後,任務階段推進到 AskBlacksmith。

Day23MultiSpeakerDialogueOrganizer.cs 第 120 到 215 行保存第一次找村長、第二次回報村長,以及洛克線索對話的逐句內容。
第 349 到 357 行的 ApplyChiefDialogueFlow() 會替同一個 ChiefInteractionArea 建立兩段對話序列。
第 387 到 395 行則為每段序列寫入自己的 requiredQuestStage、預設 speaker、互動提示、完成後是否更新目標與推進階段。
第 397 到 405 行逐句寫入 speakerName 和 text,讓村長與主角能在同一段對話中交錯出現。
這樣主線順序就固定為:
村長
→ 酒館
→ 村長
→ 武器匠
玩家不會提前看到第二次村長回報,也不會跳過酒館線索。人類終於被流程控制住了,雖然只是遊戲裡。
接著測試第二次村長回報完成後的任務更新。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373PLht4BzKJE.png

玩家完成酒館洛克對話後,回到村長家觸發第二次村長對話。
這段對話會揭露洞穴與 Boss 的舊事,也讓主角意識到普通攻擊可能無法解決問題。
對話完成後,左上角目前目標會更新為:
前往武器鋪,詢問特殊武器情報
這個目標會接到 Day 24 的武器匠鋪。
後來我把裝備方向改成拳套與符文,所以這句在後續文章中會改成「詢問拳套與符文情報」。但在 Day 23 的階段,這裡先完成的是從村長回報推進到武器匠的主線銜接。
到這裡,Day 23 前半的多 speaker 對話與任務階段流程已經完成。

接著我開始處理主角本身的操作表現。原本的主角素材比較像臨時佔位。角色移動時缺少明確方向表現,也沒有適合正式展示的攻擊動作。所以 Day 23 後半,我更換主角素材,並補上走路、跑步、四方向顯示與拳擊攻擊。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373S13TxPfWeq.png

我把主角改成專案中已匯入的 NinjaAdventure Samurai 素材。
這組素材可以提供待機、走路、跑步和攻擊 frame,比原本佔位角色更適合正式展示。
素材來源是 NinjaAdventure,授權為 Creative Commons Zero v1.0 Universal。
用途是主角待機、走路、跑步與攻擊動畫。
這個更換也配合新版拳套符文主線。主角不再需要拿刀劍在身上揮,攻擊表現改成角色本體出拳或近身攻擊。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373TID6EyLr8p.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373onI8iyvRQL.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373d39JUN31o5.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373lRiwPgku8O.png
https://ithelp.ithome.com.tw/upload/images/20260921/201783735QzzYmzsjK.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373AB1pA5wQJy.png

PlayerMovement.cs 負責玩家移動、跑步與面向紀錄。
第 7 到 19 行設定走路速度、跑步速度、目前移動輸入、目前面向和是否跑步。
第 25 到 41 行初始化 Rigidbody2D、碰撞篩選與玩家背包資料,讓移動、碰撞和裝備資料在遊戲開始時準備好。
第 43 到 59 行中,Update() 每幀讀取輸入,FixedUpdate() 則依照走路或跑步速度計算位移,再交給剛體移動。
第 61 到 69 行的 FaceDirection() 會更新角色面向,讓角色停止移動後仍然記得最後方向。
第 71 到 111 行則讀取 WASD、方向鍵與 Shift,並在有方向輸入時更新 moveInput、isRunning 和 facingDirection。
這裡把輸入和物理移動分開:
輸入放在 Update(),讓按鍵反應比較即時。
實際移動放在 FixedUpdate(),比較符合 Unity 的物理更新流程。
facingDirection 是這次主角系統的核心資料。
後面的動畫和攻擊都會讀它。玩家如果往左走後停下來,角色會記得自己最後面向左邊;按攻擊時,也會沿用這個方向,不會出現往左走、攻擊卻面朝下的現象。
接著是主角動畫腳本。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373eVAjKl19jK.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373pclf592T7q.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373KVCG8kHzZp.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373MmOFKFjM5m.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373ZHGD5KgtKE.png
https://ithelp.ithome.com.tw/upload/images/20260921/201783738emQI2rKaA.png
https://ithelp.ithome.com.tw/upload/images/20260921/201783736S6zrK9owJ.png
https://ithelp.ithome.com.tw/upload/images/20260921/201783733Drn8kbvjd.png

這次我沒有使用複雜 Animator Controller,而是用 PlayerSpriteAnimator.cs 直接控制 SpriteRenderer 換圖。
第 7 到 25 行保存 SpriteRenderer、移動狀態,以及待機、走路、跑步、攻擊 frame 陣列。
第 27 到 40 行用 FacingGroup 分成下、上、側面三組,並保存目前播放中的 frame、方向與攻擊狀態。
第 42 到 63 行初始化 SpriteRenderer 和 PlayerMovement,並停用舊 Animator,改由這支腳本直接換圖。
第 65 到 95 行在 Update() 中依照攻擊狀態或移動狀態切換動畫。
第 97 到 122 行的 PlayAttack() 會依照攻擊方向播放攻擊 frame。
第 124 到 152 行依照目前是跑步、走路或待機選擇 frame。
第 154 到 162 行依照上、下、側面選擇攻擊 frame。
第 174 到 190 行則用時間累積推進 frame,平常動畫會循環,攻擊動畫播完後回到一般狀態。

角色動畫分成兩層:
平常移動時播放待機、走路或跑步。
按下攻擊時,PlayAttack() 會短暫切換到攻擊 frame,暫時覆蓋一般移動動畫。
左右方向共用側面圖,往左時使用 flipX 翻轉。
這樣不用準備左右兩份素材,也能讓角色看起來有方向變化。
接著是攻擊腳本。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373sXDDqfHmK9.png
https://ithelp.ithome.com.tw/upload/images/20260921/201783737iUI2kTX2H.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373S1yI6nlSLQ.pnghttps://ithelp.ithome.com.tw/upload/images/20260921/201783735va4jXAmTO.png
https://ithelp.ithome.com.tw/upload/images/20260921/20178373cYKW9o8iGp.pnghttps://ithelp.ithome.com.tw/upload/images/20260921/201783736EX0A3Db81.pnghttps://ithelp.ithome.com.tw/upload/images/20260921/20178373cGeQA1i9iz.pnghttps://ithelp.ithome.com.tw/upload/images/20260921/20178373LNt0tVxPZR.png

PlayerAttack.cs 負責主角按 J 攻擊、播放攻擊表現,並對前方敵人造成傷害。
第 6 到 14 行設定攻擊範圍、冷卻、攻擊方向,以及移動、攻擊視覺和動畫引用。
第 19 到 42 行初始化移動、武器視覺、攻擊特效與角色動畫引用。
第 44 到 63 行在 Update() 中讀取鍵盤,玩家按 J 時呼叫 Attack()。
第 65 到 82 行會先檢查攻擊冷卻,避免玩家連按造成每幀都判定傷害。
第 84 到 98 行播放角色本體攻擊 frame 和前方攻擊視覺效果。
第 100 到 128 行從背包讀取拳套與符文總傷害,並用 Physics2D.OverlapCircleAll() 偵測攻擊範圍內的敵人。
第 130 到 163 行決定攻擊方向:如果玩家按著方向鍵,就用方向鍵;如果沒有,就使用 PlayerMovement.FacingDirection。
第 165 到 179 行則顯示操作提示與 Scene 視窗攻擊範圍 Gizmo。
這樣主角攻擊不是碰到怪物就自動扣血,而是有明確攻擊鍵。
玩家按 J 後,系統會確認攻擊冷卻,更新方向,播放角色攻擊 frame,再在角色前方做一次範圍判定。
傷害來自:
PlayerInventory.GetAttackDamage()
也就是目前拳套與符文提供的總傷害。
這樣主角操作、動畫、面向和裝備數值就接在一起了。主角不只是做動作,裝備也確實會影響打出去的傷害。
接著檢查 Unity 中 Player 的元件。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373KXipbFfPjG.png

完成主角替換後,Player 不再只是單一 SpriteRenderer。
它會包含:
PlayerMovement
PlayerSpriteAnimator
PlayerAttack
PlayerAttackVisual
PlayerVisual 子物件
我把主角拆成「控制」和「視覺」兩部分。
根物件保留碰撞與移動,PlayerVisual 負責顯示角色圖像。
這樣攻擊時可以只讓角色畫面短暫前衝,不會把碰撞箱一起拉走。
如果直接移動根物件,攻擊特效可能順便改變實際碰撞位置,到時候玩家還沒打怪,碰撞系統先開始發瘋。
接著測試跑步與四方向顯示。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373WYcdXkW3s1.png

進入 Play Mode 後,玩家可以用 WASD 或方向鍵移動。
按住 Shift 時,角色會切換成跑步速度,並播放跑步 frame。
往上、下、左、右不同方向移動時,角色圖像也會跟著改變。
這一步確認主角不再是固定面朝下的佔位角色,而是具備基本 RPG 操作表現。
最後測試主角拳擊攻擊。

https://ithelp.ithome.com.tw/upload/images/20260921/20178373HVyhK6di2M.png

玩家按下 J 後,主角會依照目前方向播放攻擊 frame。
同時,系統會在角色前方做一次攻擊範圍判定。如果範圍內有小怪,就會依照拳套與符文的總傷害扣血,畫面也會顯示命中訊息。這一步代表主角的動畫和戰鬥判定已經接上。主角不再只是移動角色,而是可以跑步、轉向、出拳,並用裝備資料計算傷害。

Day 23 到這裡完成了兩個重要方向。
第一,對話系統升級。
現在同一段對話可以逐句指定說話者,酒館人物、村長和主角都能用更接近劇本的方式對話。同時加入任務階段限制,讓主線固定為「村長 → 酒館 → 村長 → 武器匠」。
第二,主角操作升級。
主角更換為 NinjaAdventure Samurai 素材,支援走路、跑步、四方向顯示和拳擊攻擊。這也為後面的拳套與符文戰鬥系統打下基礎。
Day 23 還不是正式 UI 系統。
對話框目前仍然使用 OnGUI(),未來如果時間足夠,可以再換成 Canvas UI,加入角色頭像、選項分支和更完整的任務顯示。

不過就目前進度來說,Day 23 已經把劇情流程和主角操作都往前推了一大步。
接下來 Day 24 就可以進入武器匠鋪,讓主角正式接觸拳套與符文情報;Day 25 則會把這套攻擊接到森林小怪、怪物血量、掉落與精英怪門檻上。


上一篇
Day 22:建立酒館室內場景與背景人物互動
下一篇
Day 24:武器匠鋪與拳套符文主線
系列文
Unity進階——做一個完整的遊戲25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言